package br.com.model.dao;

import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Root;

import br.com.model.util.EntityManagerUtil;

/**
 * @author Lindoélio Lázaro
 */

public abstract class AbstractDao<T> {

    protected Class entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    private EntityManager em;
    protected CriteriaBuilder cb;
    protected CriteriaQuery<T> cq;
    protected Root<T> root;
    protected static String ASC = "asc";
    protected static String DESC = "desc";

    protected EntityManager getEntityManager() {
        if (em == null) {
            em = EntityManagerUtil.createEntityManager();
        }
        return em;
    }

    public void insert(T entity) throws Exception {
        try {
            getEntityManager().getTransaction().begin();
            getEntityManager().persist(entity);
            getEntityManager().getTransaction().commit();
        } catch (Exception ex) {
            getEntityManager().getTransaction().rollback();
            throw ex;
        }
    }

    public void update(T entity) throws Exception {
        this.update(entity, null);
    }

    public void update(T entity, Object id) throws Exception {
        try {
            getEntityManager().getTransaction().begin();
            if (id == null) {
                getEntityManager().merge(entity);
            } else {
                getEntityManager().merge((T) getEntityManager().find(entityClass, id));
            }
            getEntityManager().getTransaction().commit();
        } catch (Exception ex) {
            getEntityManager().getTransaction().rollback();
            throw ex;
        }
    }

    public void delete(Object id) throws Exception {
        try {
            getEntityManager().getTransaction().begin();
            getEntityManager().remove((T) getEntityManager().find(entityClass, id));
            getEntityManager().getTransaction().commit();
        } catch (Exception ex) {
            getEntityManager().getTransaction().rollback();
            throw ex;
        }
    }

    public T find(Object id) {
        return (T) getEntityManager().find(entityClass, id);
    }
    
    public List<T> find(String queryStr) {
        return this.find(queryStr, null, 0, 0);
    }
    
    public List<T> find(String queryStr, Map<String, Object> parameters) {
        return this.find(queryStr, parameters, 0, 0);
    }

    public List<T> find(String queryStr, Map<String, Object> parameters, int first, int max) {
        TypedQuery<T> typedQuery = getEntityManager().createQuery(queryStr, entityClass);
        if (parameters != null && !parameters.isEmpty()) {
            for (Entry<String, Object> entry : parameters.entrySet()) {
                typedQuery.setParameter(entry.getKey(), entry.getValue());
            }
        }
        if (first > 0) {
            typedQuery.setFirstResult(first);
        }
        if (max > 0) {
            typedQuery.setMaxResults(max);
        }
        return typedQuery.getResultList();
    }

    public List<T> list() {
        return list(new ArrayList<String[]>(), 0, 0);
    }

    public List<T> list(int firstResult, int maxResult) {
        return list(new ArrayList<String[]>(), firstResult, maxResult);
    }

    public List<T> list(List<String[]> orderByList) {
        return list(orderByList, 0, 0);
    }

    /*
     * Lista de objetos de tipo genérico <T> ordenada por "n" atributos com possibilidade de limitação "primeiro" e/ou "último"
     * @param List<String[]> orderByList Lista de atributos (colunas) para ordenação
     * @param int first Limite inferior de registros
     * @param int max Limite máximo de registros
     * @return List<T> Lista de objetos de tipo genérico <T> retornados pela consulta
     */

    public List<T> list(List<String[]> orderByList, int first, int max) {
        cb = getEntityManager().getCriteriaBuilder();
        cq = cb.createQuery(entityClass);
        root = cq.from(entityClass);
        cq.select(root);
        if (orderByList != null && !orderByList.isEmpty()) {
            List<Order> orders = new ArrayList<Order>();
            for (String[] orderStr : orderByList) {
                if (orderStr[0].toLowerCase().equals(ASC)) {
                    orders.add(cb.asc(root.get((String) orderStr[1])));
                } else if (orderStr[0].toLowerCase().equals(DESC)) {
                    orders.add(cb.desc(root.get((String) orderStr[1])));
                }
            }
            cq.orderBy(orders);
        }
        Query query = getEntityManager().createQuery(cq);
        if (first > 0) {
            query.setFirstResult(first);
        }
        if (max > 0) {
            query.setMaxResults(max);
        }
        return query.getResultList();
    }

    public int count() {
        cb = getEntityManager().getCriteriaBuilder();
        CriteriaQuery<Long> cqLong = cb.createQuery(entityClass);
        root = cqLong.from(entityClass);
        cqLong.select(cb.count(root));
        return ((Long) getEntityManager().createQuery(cqLong).getSingleResult()).intValue();
    }
}
